package com.mulong.common.util.httpclient;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.message.BasicStatusLine;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * Executor
 *
 * @author mulong
 * @date 2020-12-01 13:46:34
 */
@Slf4j
public class Executor {
    private HttpClient client;
    private HttpRequestBase request;
    /** 请求内容类型 */
    private BodyType bodyType;
    /** 请求资源地址 */
    private String url;
    /** 字符集编码 默认：UTF-8 */
    private Charset charset = Consts.UTF_8;
    /** 读取超时间 默认：30s */ 
    private Integer socketTimeout = 30*1000;
    /** 连接超时时间 默认：10s */
    private Integer connectTimeout = 10*1000;
    /** 连接池获取连接超时时间 默认：3s */
    private Integer connectionRequestTimeout = 3*1000;
    /** 头信息 */
    private List<Header> headers;
    /** 参数 */
    private Map<String, Object> params;
    /** 原生内容 */
    private String raw;
    /** 是否开始Gzip压缩 */
    private boolean onGzip = false;
    /** 是否打印返回内容 */
    private boolean logContent = false;

    public Executor(HttpClient client, HttpRequestBase request, BodyType bodyType) {
        this.client = client;
        this.request = request;
        this.bodyType = bodyType;
        this.url = request.getURI().toString();
    }

    public Executor charset(Charset charset) {
        this.charset = charset;
        return this;
    }

    public Executor socketTimeout(int socketTimeout) {
        this.socketTimeout = socketTimeout;
        return this;
    }

    public Integer getSocketTimeout() {
        return socketTimeout;
    }

    public Executor connectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
        return this;
    }

    public Integer getConnectTimeout() {
        return connectTimeout;
    }

    public Executor connectionRequestTimeout(int connectionRequestTimeout) {
        this.connectionRequestTimeout = connectionRequestTimeout;
        return this;
    }

    public Executor headers(Map<String, String> headers) {
        if (MapUtils.isEmpty(headers)) {
            return this;
        }
        if (this.headers == null) {
            this.headers = new ArrayList<>();
        }
        headers.forEach((name, value) -> this.headers.add(new BasicHeader(name, value)));
        return this;
    }

    public Executor header(String name, String value) {
        if (this.headers == null) {
            this.headers = new ArrayList<>();
        }
        this.headers.add(new BasicHeader(name, value));
        return this;
    }

    public Executor params(Map<String, Object> params) {
        this.params = params;
        return this;
    }

    public Executor param(String name, String value) {
        if (this.params == null) {
            this.params = new HashMap<>();
        }
        params.put(name, value);
        return this;
    }

    public Map<String, Object> getParams() {
        return params;
    }

    public Executor raw(String raw) {
        this.raw = raw;
        return this;
    }

    public Executor enableGzip() {
        this.onGzip = true;
        return this;
    }

    public Executor logContent() {
        this.logContent = true;
        return this;
    }

    public StringResponse execute() throws Exception {
        // 设置config
        setConfig();
        // 设置header
        setHeader();
        // 设置body
        setBody();

        StringResponse stringResponse = null;
        long timeMillis = System.currentTimeMillis();
        try {
            // 打印请求
            logRequest();
            // 执行请求
            HttpResponse response = client.execute(request);
            // 解析结果
            stringResponse = handleResponse(response);
        } catch (Exception e) {
            stringResponse = new StringResponse(e);
            throw e;
        } finally {
            // 统计耗时
            timeMillis = System.currentTimeMillis() - timeMillis;
            stringResponse.setCostTime(timeMillis);
            // 打印响应
            logResponse(timeMillis, stringResponse);
        }
        return stringResponse;
    }

    /**
     * 设置config
     */
    private void setConfig() {
        RequestConfig.Builder builder = RequestConfig.custom();
        // 设置连接超时时间
        if (connectTimeout > 0) {
            builder.setConnectTimeout(connectTimeout);
        }
        // 设置读取超时时间
        if (socketTimeout > 0) {
            builder.setSocketTimeout(socketTimeout);
        }
        // 设置连接池获取连接超时时间
        if (connectionRequestTimeout > 0) {
            builder.setConnectionRequestTimeout(connectionRequestTimeout);
        }
        RequestConfig config = builder.build();
        request.setConfig(config);
    }

    /**
     * 设置header
     */
    private void setHeader() {
        if (CollectionUtils.isNotEmpty(headers)) {
            request.setHeaders(headers.toArray(new Header[0]));
        }
        request.setHeader(HttpHeaders.CONNECTION, HTTP.CONN_KEEP_ALIVE);
        if (this.onGzip) {
            request.setHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
        }
    }

    /**
     * 设置body
     */
    private void setBody() throws IOException {
        HttpEntity reqEntity = null;
        if (bodyType == BodyType.FORM) {
            if (MapUtils.isNotEmpty(params)) {
                reqEntity = new UrlEncodedFormEntity(Objects.requireNonNull(map2NameValuePairList(params)), charset);
                if (!url.endsWith("?")) {
                    url = url + "?";
                }
                url = url + EntityUtils.toString(reqEntity);
                if (request instanceof HttpGet) {
                    request.setURI(URI.create(url));
                }
            }
        } else if (bodyType == BodyType.JSON) {
            ContentType contentType = ContentType.APPLICATION_JSON.withCharset(charset);
            request.setHeader(HttpHeaders.CONTENT_TYPE, contentType.toString());
            reqEntity = new StringEntity(raw, contentType);
        } else if (bodyType == BodyType.XML) {
            ContentType contentType = ContentType.APPLICATION_XML.withCharset(charset);
            request.setHeader(HttpHeaders.CONTENT_TYPE, contentType.toString());
            reqEntity = new StringEntity(raw, contentType);
        }
        if (reqEntity != null && request instanceof HttpPost) {
            ((HttpPost) request).setEntity(reqEntity);
        }
    }

    /**
     * 解析结果
     */
    private StringResponse handleResponse(HttpResponse response) throws IOException {
        HttpEntity resEntity = response.getEntity();
        String contentString = null;
        if (resEntity != null) {
            if (resEntity.getContentEncoding() != null && "gzip".equals(resEntity.getContentEncoding().getValue())) {
                resEntity = new GzipDecompressingEntity(resEntity);
            }
            contentString = EntityUtils.toString(resEntity, charset);
        }
        EntityUtils.consume(resEntity);
        StatusLine statusLine = response.getStatusLine();
        int statusCode = statusLine.getStatusCode();
        String reasonPhrase = statusLine.getReasonPhrase();
        if (StringUtils.isBlank(reasonPhrase)) {
            HttpStatus httpStatus = HttpStatus.valueOf(statusCode);
            if (httpStatus != null) {
                reasonPhrase = httpStatus.getReasonPhrase();
            }
        }
        statusLine = new BasicStatusLine(statusLine.getProtocolVersion(), statusCode, reasonPhrase);
        return new StringResponse(contentString, statusLine);
    }

    /**
     * 打印请求
     */
    private void logRequest() {
        StringBuilder logSb = new StringBuilder();
        logSb.append("Request  >> ");
        logSb.append("method: ").append(request.getMethod());
        logSb.append(" - url: ").append(url);
        if (bodyType == BodyType.JSON || bodyType == BodyType.XML) {
            logSb.append(" - body: ").append(raw);
        }
        log.info(logSb.toString());
    }

    /**
     * 打印响应
     */
    private void logResponse(long timeMillis, StringResponse stringResponse) {
        StringBuilder logSb = new StringBuilder();
        logSb.append("Response << ");
        logSb.append("time: ").append(timeMillis);
        if (stringResponse.getStatusLine() != null) {
            logSb.append(" - status: ").append(stringResponse.getStatusLine().getStatusCode());
            logSb.append(" - reason: ").append(stringResponse.getStatusLine().getReasonPhrase());
        }
        if (stringResponse.getException() != null) {
            logSb.append(" - exception: ").append(ExceptionUtils.getRootCauseMessage(stringResponse.getException()));
        }
        if (logContent) {
            logSb.append(" - content: ").append(stringResponse.getContentString());
        }
        log.info(logSb.toString());
    }

    /**
     * 参数转换
     */
    private List<NameValuePair> map2NameValuePairList(Map<String, ?> params) {
        if (MapUtils.isNotEmpty(params)) {
            List<NameValuePair> list = new ArrayList<>();
            params.forEach((k, v) -> {
                if (v != null) {
                    list.add(new BasicNameValuePair(k, String.valueOf(v)));
                }
            });
            return list;
        }
        return null;
    }

}
